package com.cn.easy.export.service.excel

import com.cn.easy.export.model.excel.*
import com.cn.easy.export.model.excel.CellRange
import com.cn.easy.export.util.autoWidthAndHeight
import com.cn.easy.export.util.setCellValue
import org.apache.poi.ss.usermodel.*
import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.RegionUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

class ExcelExportService(
    vararg params: ExportParam<*>,
) {

    val logger: Logger = LoggerFactory.getLogger(ExcelExportService::class.java)

    private var paramsArr: Array<out ExportParam<*>> = params
    private val workbook: Workbook = XSSFWorkbook()


    fun export(): Workbook {
        paramsArr.forEach {
            HandleCreateSheet(workbook, it).writeSheet()
        }

        return workbook
    }

    private class HandleCreateSheet<Row>(
        private val workbook: Workbook,
        private val param: ExportParam<Row>,
    ) {
        val logger: Logger = LoggerFactory.getLogger(HandleCreateSheet::class.java)

        private val sheet: Sheet = param.sheetName?.let {
            workbook.createSheet(param.sheetName)
        } ?: workbook.createSheet()
        private val defaultTitleStyle: CellStyle
        private val defaultHeaderStyle: CellStyle
        private val defaultCellStyle: CellStyle
        private val dataList = arrayListOf<ExcelCoordinateValue<*>>()
        private val cellRangeList = arrayListOf<CellRange>()

        /**
         * Header深度
         */
        private val depth: Int

        /**
         * Header广度
         */
        private val breadth: Int

        /**
         * 数据开始行数
         */
        private val dataStartRowIndex: Int

        var nextRowStartIndex = 0

        init {
            depth = headerDepth(param.columns)
            breadth = breadth(param.columns)
            dataStartRowIndex = depth + if (param.excelTitle != null) 1 else 0
            defaultTitleStyle = createDefaultTitleStyle()
            defaultHeaderStyle = param.headerStyle?.let { it(workbook) } ?: createDefaultCellStyle()
            defaultCellStyle = param.cellStyle?.let { it(workbook) } ?: createDefaultCellStyle()
        }

        fun createDefaultCellStyle(): CellStyle {
            val cellStyle = workbook.createCellStyle()
            cellStyle.alignment = HorizontalAlignment.CENTER
            return cellStyle
        }

        fun createDefaultTitleStyle(): CellStyle {
            val cellStyle = workbook.createCellStyle()
            cellStyle.alignment = HorizontalAlignment.CENTER

            val font = workbook.createFont()
            font.bold = true
            font.fontName = "微软雅黑"
            font.fontHeightInPoints = 24
            cellStyle.setFont(font)

            return cellStyle
        }

        fun writeSheet() {

            val createTitle = createTitle(workbook, param.excelTitle)
            if (createTitle) {
                nextRowStartIndex++
            }

            createHeader(nextRowStartIndex, param.columns)

            nextRowStartIndex += depth

            createValue(nextRowStartIndex, param.columns)

            nextRowStartIndex += param.list.size

            addSum()

            dataList.forEach { logger.debug(it.toString()) }

            param.customAction?.let { it(sheet, nextRowStartIndex, dataList) }

            writeData(sheet)

            autoWidthAndHeight(dataList, sheet, dataStartRowIndex)

            cellRangeList.forEach {
                sheet.addMergedRegion(it.cellRangeAddress)
                setRangeAddressColBorderStyle(it.cellRangeAddress, it.cell, it.cellStyle)
            }
        }

        private fun addSum() {
            if (param.sumColumns.isNotEmpty()) {
                param.sumColumns.forEach { sumColumn ->
                    dataList.add(
                        ExcelCoordinateValue(
                            ExcelCoordinate(nextRowStartIndex + sumColumn.coordinate.row, sumColumn.coordinate.col),
                            ExcelCoordinate(nextRowStartIndex + sumColumn.coordinate.row, sumColumn.coordinate.col),
                            sumColumn.title.value,
                            sumColumn.title.cellStyle?.let {
                                it(workbook)
                            } ?: defaultHeaderStyle
                        )
                    )
                    dataList.add(
                        ExcelCoordinateValue(
                            ExcelCoordinate(nextRowStartIndex + sumColumn.coordinate.row, sumColumn.coordinate.col + 1),
                            ExcelCoordinate(nextRowStartIndex + sumColumn.coordinate.row, sumColumn.coordinate.col + 1),
                            sumColumn.value,
                            sumColumn.cellStyle
                        )
                    )
                }
            }
        }

        private fun writeData(sheet: Sheet) {

            dataList.forEach { data ->
                val (rowIndex, colIndex) = data.coordinate1
                val row = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex)
                val cell = row.getCell(colIndex) ?: row.createCell(colIndex)

                setCellValue(cell, data.value)
                data.cellStyle?.let {
                    cell.cellStyle = it
                }

                addCellRangeAddress(cell, data)
            }
        }

        /**
         * 合并单元格
         */
        private fun addCellRangeAddress(cell: Cell, data: ExcelCoordinateValue<*>) {
            val (rowIndex1, colIndex1) = data.coordinate1
            val (rowIndex2, colIndex2) = data.coordinate2

            if (abs(rowIndex2 - rowIndex1) > 0 || abs(colIndex2 - colIndex1) > 0) {

                cellRangeList.add(
                    CellRange(
                        cell,
                        cell.cellStyle,
                        CellRangeAddress(
                            min(rowIndex1, rowIndex2),
                            max(rowIndex1, rowIndex2),
                            min(colIndex1, colIndex2),
                            max(colIndex1, colIndex2)
                        )
                    )
                )
            }
        }

        private fun createValue(rowStartIndex: Int, columns: List<ExcelColumn<Row>>) {
            fun forEachValue(
                rowData: Row,
                columns: List<ExcelColumn<Row>>,
                startRow: Int,
                startCol: Int,
            ) {
                var index = 0
                columns.forEach { column ->
                    val cellStyle = column.cellStyle?.let { it(workbook, rowData) } ?: defaultCellStyle
                    when (column) {
                        is SingleExcelColumn<Row, *> -> {
                            dataList.add(
                                ExcelCoordinateValue(
                                    ExcelCoordinate(startRow, startCol + index),
                                    ExcelCoordinate(startRow, startCol + index),
                                    column.value(rowData),
                                    cellStyle
                                )
                            )
                            index++
                        }

                        is SingleRowMultiExcelColumn -> {
                            forEachValue(rowData, column.children, startRow, startCol + index)
                            index += breadth(column.children)
                        }

                        else -> {
                            throw RuntimeException("存在需要处理的ExcelColumn")
                        }
                    }
                }
            }

            param.list.forEachIndexed { index, rowData ->
                forEachValue(rowData, columns, rowStartIndex + index, 0)
            }
        }

        /**
         * 创建表格头
         */
        private fun <Row> createHeader(nextRowStartIndex: Int, columns: List<ExcelColumn<Row>>) {
            forEachHeader(columns, nextRowStartIndex, 0, depth - 1)
        }

        private fun forEachHeader(columns: List<ExcelColumn<*>>, startRow: Int, startCol: Int, depth: Int) {
            var index = 0
            columns.forEach { column ->
                when (column) {
                    is SingleExcelColumn<*, *> -> {
                        dataList.add(
                            ExcelCoordinateValue(
                                ExcelCoordinate(startRow, startCol + index),
                                ExcelCoordinate(startRow + depth, startCol + index),
                                column.title.value,
                                column.title.cellStyle?.let { it(workbook) } ?: defaultHeaderStyle
                            )
                        )
                        index++
                    }


                    is SingleRowMultiExcelColumn -> {
                        val breadth = breadth(arrayListOf(column))
                        dataList.add(
                            ExcelCoordinateValue(
                                ExcelCoordinate(startRow, startCol + index),
                                ExcelCoordinate(startRow, startCol + index + breadth - 1),
                                column.title.value,
                                column.title.cellStyle?.let { it(workbook) } ?: defaultHeaderStyle
                            )
                        )
                        forEachHeader(column.children, startRow + 1, startCol + index, depth - 1)
                        index += breadth
                    }

                    else -> {
                        throw RuntimeException("存在需要处理的ExcelColumn")
                    }
                }
            }
        }

        /**
         * 创建标题
         */
        private fun createTitle(
            workbook: Workbook,
            excelTitle: ExcelTitle?,
        ): Boolean {
            return excelTitle?.let {
                val titleRow = sheet.createRow(0)

                val titleCell = titleRow.createCell(0)
                titleCell.setCellValue(it.value)
                titleCell.cellStyle = it.cellStyle?.let { style ->
                    style(workbook)
                } ?: defaultTitleStyle

                val cra = CellRangeAddress(0, 0, 0, breadth - 1)
                cellRangeList.add(CellRange(titleCell, titleCell.cellStyle, cra))
                true
            } == true
        }

        private fun setRangeAddressColBorderStyle(colCra: CellRangeAddress, cell: Cell, cellStyle: CellStyle) {
            val sheet = cell.sheet
            RegionUtil.setBorderTop(cellStyle.borderTop, colCra, sheet);
            RegionUtil.setBorderLeft(cellStyle.borderLeft, colCra, sheet);
            RegionUtil.setBorderRight(cellStyle.borderRight, colCra, sheet);
            RegionUtil.setBorderBottom(cellStyle.borderBottom, colCra, sheet);
        }

        /**
         * 广度计算
         */
        private fun breadth(columns: List<ExcelColumn<*>>): Int {
            return columns.sumOf {
                when (it) {
                    is SingleRowMultiExcelColumn -> {
                        breadth(it.children)
                    }

                    else -> {
                        1
                    }
                }
            }
        }

        private fun headerDepth(root: ExcelColumn<*>): Int {
            var maxChildDepth = 0;
            return when (root) {
                is SingleRowMultiExcelColumn -> {
                    val children = root.children
                    for (child in children.reversed()) {
                        val childDepth = headerDepth(child);
                        maxChildDepth = max(maxChildDepth, childDepth)
                    }
                    maxChildDepth + 1
                }

                else -> {
                    1
                }
            }
        }


        /**
         * 深度计算
         */
        private fun headerDepth(columns: List<ExcelColumn<*>>): Int {
            var maxChildDepth = 0;

            for (excelColumn in columns.reversed()) {
                maxChildDepth = max(headerDepth(excelColumn), maxChildDepth)
            }
            return maxChildDepth
        }
    }
}